/**
 * 1. make
 * 2. ./mpp-multi-thread-demo
 * 3. gst-launch-1.0 filesrc location=Tennis1080p.yuv ! videoparse width=1920 height=1080 format=nv12 ! videoconvert ! xvimagesink
 */
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <glib.h>
#include <rockchip/rk_mpi.h>

#define __IN_FILE__ ("Tennis1080p.h264")
#define __OUT_FILE__ ("Tennis1080p.yuv")
#define __MPP_PACKET_BUFFER_SIZE__ (4 * 1024 * 1024)
// #define __THREAD_DETATCH__

typedef struct MppDecoder {
    MppCtx ctx_;
    MppApi *mpi_;
    MppPacket pkt_;
    uint8_t *pkt_buffer_;

    pthread_t send_thread_id_;
    pthread_t recv_thread_id_;
} MppDecoder;

void dump_frame(MppFrame frame, FILE *out_fp)
{
    printf("dump_frame_to_file\n");

    RK_U32 width    = 0;
    RK_U32 height   = 0;
    RK_U32 h_stride = 0;
    RK_U32 v_stride = 0;
    MppFrameFormat fmt  = MPP_FMT_YUV420SP;
    MppBuffer buffer    = NULL;
    RK_U8 *base = NULL;

    width    = mpp_frame_get_width(frame);
    height   = mpp_frame_get_height(frame);
    h_stride = mpp_frame_get_hor_stride(frame);
    v_stride = mpp_frame_get_ver_stride(frame);
    fmt      = mpp_frame_get_fmt(frame);
    buffer   = mpp_frame_get_buffer(frame);

    RK_U32 buf_size = mpp_frame_get_buf_size(frame);
    printf("w x h: %dx%d hor_stride:%d ver_stride:%d buf_size:%d\n",
           width, height, h_stride, v_stride, buf_size);
           
    if (NULL == buffer) {
        printf("buffer is null\n");
        return ;
    }

    base = (RK_U8 *)mpp_buffer_get_ptr(buffer);

    // MPP_FMT_YUV420SP
    if (fmt != MPP_FMT_YUV420SP) {
        printf("fmt %d not supported\n", fmt);
        return;
    }

    RK_U32 i;
    RK_U8 *base_y = base;
    RK_U8 *base_c = base + h_stride * v_stride;

    for (i = 0; i < height; i++, base_y += h_stride) {
        fwrite(base_y, 1, width, out_fp);
    }
    for (i = 0; i < height / 2; i++, base_c += h_stride) {
        fwrite(base_c, 1, width, out_fp);
    }
}

void dump_frame_to_file(MppDecoder* decoder, MppFrame frame, FILE *out_fp)
{
    printf("decode_and_dump_to_file\n");
    g_return_if_fail(decoder != NULL);
    g_return_if_fail(frame != NULL);

    MPP_RET ret;

    if (mpp_frame_get_info_change(frame)) {
        printf("mpp_frame_get_info_change\n");
        /**
         * 第一次解码会到这个分支，需要为解码器设置缓冲区.
         * 解码器缓冲区支持3种模式。参考【图像内存分配以及交互模式】Rockchip_Developer_Guide_MPP_CN.pdf
         * 这里使用纯内部模式。
         */
        ret = decoder->mpi_->control(decoder->ctx_, MPP_DEC_SET_INFO_CHANGE_READY, NULL);
        if (ret) {
            printf("mpp_frame_get_info_change mpi->control error"
                    "MPP_DEC_SET_INFO_CHANGE_READY %d\n", ret);
        }
        return;
    }

    RK_U32 err_info = mpp_frame_get_errinfo(frame);
    RK_U32 discard = mpp_frame_get_discard(frame);    
    printf("err_info: %u discard: %u\n", err_info, discard);

    if (err_info) {
        return;
    }
        
    // save
    dump_frame(frame, out_fp);
    return;
}

void *send_worker(void *arg)
{
    g_return_val_if_fail(arg != NULL, NULL);
    MppDecoder* decoder = arg;

    // 打开输入文件
    FILE *in_fp = fopen(__IN_FILE__, "rb");
    if (!in_fp) {
        printf("fopen error\n");
        return NULL;
    }

    while (1) {
        // 清理pkt
        if (decoder->pkt_) {
            mpp_packet_deinit(&decoder->pkt_);
            decoder->pkt_ = NULL;
        }

        if (decoder->pkt_buffer_) {
            free(decoder->pkt_buffer_);
            decoder->pkt_buffer_ = NULL;
        }

        // 读取数据
        int len = BUFSIZ;
        uint8_t *buffer = (uint8_t*)malloc(sizeof(uint8_t) * len);
        len = fread(buffer, 1, len, in_fp);
        printf("read :%d\n", len);
        if (len <= 0) {
            break;
        }

        mpp_packet_init(&decoder->pkt_, buffer, len);
        decoder->pkt_buffer_ = buffer;

        if (len < BUFSIZ || feof(in_fp))
            mpp_packet_set_eos(decoder->pkt_);

        MPP_RET ret = !MPP_OK;
        while (MPP_OK != ret) {
            ret = decoder->mpi_->decode_put_packet(decoder->ctx_, decoder->pkt_);
            if (MPP_OK != ret) {
                printf("decode_put_packet falied remain:%d\n", 
                       mpp_packet_get_length(decoder->pkt_));            
                continue;
            }
            printf("decode_put_packet success remain:%d\n", 
                   mpp_packet_get_length(decoder->pkt_));        
        }
    }

    fclose(in_fp);
    return NULL;
}

void *recv_worker(void *arg)
{
    g_return_val_if_fail(arg != NULL, NULL);
    MppDecoder* decoder = arg;
    FILE *out_fp = fopen(__OUT_FILE__, "wb+");
    if (!out_fp) {
        printf("fopen error\n");
        return NULL;
    }

    while (1) {
        printf("decode_get_frame\n");
        MppFrame frame;
        MPP_RET ret = decoder->mpi_->decode_get_frame(decoder->ctx_, &frame);
        if (MPP_OK != ret || !frame) {
            printf("decode_get_frame falied ret:%d\n", ret);
            usleep(2000);
            continue;
        }

        printf("decode_get_frame success\n");

        dump_frame_to_file(decoder, frame, out_fp);
        if (mpp_frame_get_eos(frame)) {
            printf("mpp_frame_get_eos\n");
            mpp_frame_deinit(&frame);
            break;
        }

        mpp_frame_deinit(&frame);
    }

    fclose(out_fp);
    return NULL;
}

static void mpp_decoder_destroy(MppDecoder* decoder)
{
    g_return_if_fail(decoder != NULL);

    if (decoder->send_thread_id_) {
        pthread_cancel(decoder->send_thread_id_);
        decoder->send_thread_id_ = 0;
    }

    if (decoder->recv_thread_id_) {
        pthread_cancel(decoder->recv_thread_id_);
        decoder->recv_thread_id_ = 0;
    }

    if (decoder->mpi_) {
        decoder->mpi_->reset(decoder->ctx_);
        decoder->mpi_ = NULL;
    }
    
    if (decoder->ctx_) {
        mpp_destroy(decoder->ctx_);
        decoder->ctx_ = NULL;
    }

    if (decoder->mpi_) {
        mpp_packet_deinit(&decoder->pkt_);
        decoder->pkt_ = NULL;
    }

    if (decoder->pkt_buffer_) {
        free(decoder->pkt_buffer_);
        decoder->pkt_buffer_ = NULL;
    }
}

static MppDecoder* mpp_decoder_create()
{
    MppDecoder*decoder = g_new0(MppDecoder, 1);
    if (!decoder) {
        printf("g_new0 error\n");
        mpp_decoder_destroy(decoder);
        return NULL;
    }

    /* ---------- mpp decoder ctx ---------- */
    MPP_RET ret = mpp_create(&decoder->ctx_, &decoder->mpi_);
    if (MPP_OK != ret) {
        printf("mpp_create error\n");
        mpp_decoder_destroy(decoder);
        return NULL;
    }

    /**
     * 配置解器
     *      - 解码文件需要 split 模式
     *      - 设置非阻塞模式，0 非阻塞（默认），-val 阻塞，+val 超时(ms)
     */
    RK_U32 need_split = 1;
    ret = decoder->mpi_->control(
        decoder->ctx_, MPP_DEC_SET_PARSER_SPLIT_MODE, &need_split);
    if (MPP_OK != ret) {
        printf("decoder->mpi_->control MPP_DEC_SET_PARSER_SPLIT_MODE error\n");
        mpp_decoder_destroy(decoder);
        return NULL;
    }

    // 输入超时,阻塞
    MppPollType time_out = -1;
    ret = decoder->mpi_->control(
        decoder->ctx_, MPP_SET_INPUT_TIMEOUT, &time_out);
    if (MPP_OK != ret) {
        printf("decoder->mpi_->control MPP_SET_INPUT_TIMEOUT error\n");
        mpp_decoder_destroy(decoder);
        return NULL;
    }

    // 输出超时
#if 0  // 0， 输出保持非阻塞
    time_out = 0;
    ret = decoder->mpi_->control(
        decoder->ctx_, MPP_SET_OUTPUT_TIMEOUT, &time_out);
    if (MPP_OK != ret) {
        printf("decoder->mpi_->control MPP_SET_OUTPUT_TIMEOUT error\n");
        mpp_decoder_destroy(decoder);
        return NULL;
    }
#endif

    // mpp 初始化，解码器， h264
    ret = mpp_init(decoder->ctx_, MPP_CTX_DEC, MPP_VIDEO_CodingAVC);  // 固定为H264
    if (MPP_OK != ret) {
        printf("mpp_init error\n");
        mpp_decoder_destroy(decoder);
        return NULL;
    }

    /* ---------- muliti thread and semaphore ---------- */
    // thread
#ifdef __THREAD_DETATCH__
    pthread_attr_t attr;
    ret = pthread_attr_init(&attr);
    if (0 != ret) {
        printf("pthread_attr_init error\n");
        mpp_decoder_destroy(decoder);
        return NULL;
    }

    ret = pthread_attr_setdetachstate(&attr, 1);
    if (0 != ret) {
        printf("pthread_attr_setdetachstate error\n");
        mpp_decoder_destroy(decoder);
        return NULL;
    }
    ret = pthread_create(&decoder->send_thread_id_, &attr, send_worker, decoder);
    if (0 != ret) {
        printf("pthread_create send_worker error\n");
        mpp_decoder_destroy(decoder);
        return NULL;
    }

    ret = pthread_create(&decoder->recv_thread_id_, &attr, recv_worker, decoder);
    if (0 != ret) {
        printf("pthread_create recv_worker error\n");
        mpp_decoder_destroy(decoder);
        return NULL;
    }
    pthread_attr_destroy(&attr);
#else
    ret = pthread_create(&decoder->send_thread_id_, NULL, send_worker, decoder);
    if (0 != ret) {
        printf("pthread_create send_worker error\n");
        mpp_decoder_destroy(decoder);
        return NULL;
    }

    ret = pthread_create(&decoder->recv_thread_id_, NULL, recv_worker, decoder);
    if (0 != ret) {
        printf("pthread_create recv_worker error\n");
        mpp_decoder_destroy(decoder);
        return NULL;
    }
#endif

    return decoder;
}

int main(void)
{
    printf("---------- mpp start ----------\n");

    MppDecoder *decoder = mpp_decoder_create();

#ifdef __THREAD_DETATCH__
    printf("wait....\n");
    getchar();
#else
    printf("wait send_thread_id_ ....\n");
    pthread_join(decoder->send_thread_id_, NULL);
    printf("wait recv_thread_id_ ....\n");
    pthread_join(decoder->recv_thread_id_, NULL);
#endif

    mpp_decoder_destroy(decoder);
    
    printf("---------- mpp over ----------\n");
    return 0;
}
